package a1;

import java.awt.Color;

import javax.swing.JFrame;

public class Model extends Thread {
	
	private int mode;
	private int grayscaleMode;
	//private Color colors[];
	
	public int getGrayscaleMode() {
		return this.grayscaleMode;
	}
	
	public void setGrayscaleMode(int grayscaleMode) {
		this.grayscaleMode = grayscaleMode;
	}
	
	public int getMode() {
		return this.mode;
	}
	
	public void setMode(int mode) {
		this.mode = mode;
	}

	public Model() {
		super(); // common idiom to call super first when extending
		this.mode = Constants.RGB_MODE;
		this.grayscaleMode = Constants.AVERAGE_MODE;
		//this.colors = new Color[Constants.MAX_COLOR_FIELDS];
	}
	
	public void run() {
		View v = new View(this);
		// TODO passed Model to the view. The reason for this is because the MODEL should
		// actually be doing the brainwork regarding checking if a color is actually grayscale
		// safe. This still needs to be implemented.
		
		// pack and show
		v.pack();
		v.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		v.setLocationRelativeTo(null);
		v.setResizable(false);
		v.setVisible(true);
	}
	
	/**
	 * @param args
	 */
	public static void main(String[] args) {

		Model m = new Model();
		
		/*
		 *  The view should be instantiated on the run() method (which is invoked by start()),
		 *  as we want to spawn a new thread for the GUI that is separate from the Model, 
		 *  so as to not block the main thread.
		 */
		 
		m.start();

	}


	//min-max average conversion
	public static Color toGrayscaleLightness(Color color)
	{
		int min = Math.min(Math.min(color.getRed(), color.getGreen()), color.getBlue());
		int max = Math.max(Math.max(color.getRed(), color.getGreen()), color.getBlue());
		int c = (min + max) / 2;
		return new Color(c,c,c);
	}

	//average conversion
	public static Color toGrayscaleAverage(Color color)
	{
		int c = (color.getRed() + color.getGreen() + color.getBlue()) / 3;
		return new Color(c,c,c);
	}

	//weighted conversion
	//also uses a method to convert from colorspace and back for more accurate results
	public static Color toGrayscaleLuminosity(Color color)
	{

		int c = (int)Math.round(Math.pow(0.2126f * Math.pow((double)color.getRed(), 2.2f) +  0.7152f * Math.pow((double)color.getGreen(), 2.2f) + 0.0722f * Math.pow((double)color.getBlue(), 2.2f), ((double)1)/2.2f));
		return new Color(c,c,c);
	}

	//go-between method
	public static Color toGrayscale(Color color, int mode) throws IllegalStateException
	{
		if (mode == Constants.MODE_LIGHTNESS)
			return Model.toGrayscaleLightness(color);
		else if (mode == Constants.MODE_AVERAGE) //else redundant but customary
			return Model.toGrayscaleAverage(color);
		else if (mode == Constants.MODE_LUMINOSITY)
			return Model.toGrayscaleLuminosity(color);

		throw new IllegalStateException(Constants.ILLEGAL_GRAYSCALE_MODE);
	}

	public static String makeText(int mode, Color color) {
		if (mode == Constants.HEX_MODE) {
			return Model.makeHexText(color);
		} else if (mode == Constants.RGB_MODE) {
			return Model.makeRGBText(color);
		} else {
			try {
				throw new Exception(Constants.ILLEGAL_COLOR_MODE + mode);
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
		return ""; // should never run, but it's needed
	}
	
	public static String makeRGBText(Color color) {
		int red = color.getRed();	
		int green = color.getGreen();
		int blue = color.getBlue();
		
		// since Color automatically converts the color to proper color values,
		// we just add brackets and we're done
		return "(" + red + ", " + green + ", " + blue + ")";
	}

	public static String makeHexText(Color color) {
		
		String text = Integer.toHexString(color.getRGB() & 0xFFFFFF);
		// & 0xFFFFFF removes the alpha channel from the color (it's a mask)
		
		if(text.length() < 6) { // pad with 0's if not long enough
			text = "000000".substring(0, 6 - text.length()) + text;
		}
		
		return "#" + text.toUpperCase();
	}
	
	/**
	 * Parses a String of HEX format. Comfirmed working for the following inputs:
	 * <ul>
	 * <li>#FF00FF</li>
	 * <li>#1C34A67B</li>
	 * <li>#1A34C6   [any number of spaces at the end]</li>
	 * <li>#1C34A67B  [any number of spaces at the end]</li>
	 * <li>1A34C6</li>
	 * <li>1C34A67B</li>
	 * <li>1A34C6   [any number of spaces at the end]</li>	
	 * <li>1C34A67B  [any number of spaces at the end]</li>
	 * </ul>
	 * @param s The String to be parsed
	 * @return A color made from decoding the String
	 * @throws NumberFormatException When the Color cannot be parsed from the String
	 */
	public static Color parseHEX(String s) throws NumberFormatException {
		
		if (s.charAt(0) != '#') {
		// make sure the color starts with a hashmark for decode to work
			s = '#' + s;
		}
		
		return Color.decode(s.trim()); // remove whitespace before decoding
	}
	
	/** 
	 * Parses a String of RGB format. Confirmed working for the following inputs:
	 * <ul>
	 * 	<li>(123,231,255)</li>
	 * 	<li>(123, 231, 255)</li>
	 * 	<li>( 123 , 231 , 255 )   [any number of spaces at the end]</li>
	 * 	<li>( 123 , 231 , 255 )   321</li>
	 * 	<li>123,231,255</li>
	 * 	<li>123, 231, 255</li>
	 * 	<li> 123, 231, 255    [any number of spaces at the end]</li>
	 * 	<li> 123, 231, 255    321</li>
	 * </ul>
	 * @param s the String to parse
	 * @return a Color that is made from the RGB values of the String, or null if s cannot be parsed
	 */
	public static Color parseRGB(String s) {
		String result[] = new String[s.length()]; 	// max splits must be the length of the string
		result = s.split("[(), ]");			// split the string using "(", ")", "," and " " (space)
		
		if (result.length < 3) {
			// can't parse 3 colors if result wasn't divided into 3 parts
			return null;
		}
		
		int rgb[] = new int[3];				// this will hold red, green and blue values
											// NOT a magic number, since it will always be 3
		int index = 0;						// will be incremented when a valid integer is parsed
		
		for (int i = 0; i < result.length && index < rgb.length; i++) {
			// Not only do we have to check for i being less than the number of results we received
			// but we also have to check that index is valid, or we get an IndexOutOfBoundsException
			try {
				int val = Integer.parseInt(result[i]);	// attempt to parse the integer
				rgb[index++] = val;		// if an exception hasn't been thrown yet, it must be valid!
			}
			catch (NumberFormatException e) {} 		// do nothing on thrown exception
		}
		
		//in the event the color was changed to an illegal value, correct it to closest legal value.
		//not magic numbers because the rules of the RGB model are clearly defined and unchanging.
		return new Color(Math.max(Math.min(rgb[0], 255), 0), Math.max(Math.min(rgb[1], 255), 0), Math.max(Math.min(rgb[2], 255), 0));
	}
	
	/**
	 * Parses a String for a Color.
	 * @param s The String to parse
	 * @return a Color associated with the parsed String, or null if the String cannot be parsed
	 */
	public static Color parseText(String s) {
		// first try to parse in HEX, then try RGB
		Color c;
		try {
			c = Model.parseHEX(s);
			return c;
		} catch (Exception e) {
			// couldn't decode as HEX, let's try RGB
			System.out.println("Couldn't decode HEX, let's try RGB");
			try {
				c = Model.parseRGB(s);
				return c;
			} catch (Exception e1) {
				System.out.println("Couldn't decode color from string " + s);
				return null;
			}
		}
	}
	
	public static Color parseText(int mode, String s) {
		if (mode == Constants.HEX_MODE) {
			return Model.parseHEX(s);
		} else {
			return Model.parseRGB(s);
		}
	}

	public static boolean isColorUnique(Color color, Color[] allColors) {
		// TODO Auto-generated method stub
		// perfect use scenario for a for each loop :)
		// since colors are in grayscale before coming to this function
		// simply test only one value (since gray appears when all values 
		// are equal
		for (Color c : allColors) {		
			System.out.print("Comparing: " + color.getRed() +","+ color.getGreen() +","+ color.getBlue());
			System.out.println(" to " + c.getRed() +","+ c.getGreen() +","+ c.getBlue());
			if ((Math.abs(c.getRed() - color.getRed()) < Constants.COLOR_PERCEPTION_DISTANCE - 1) 
				|| (Math.abs(color.getRed() - c.getRed()) < Constants.COLOR_PERCEPTION_DISTANCE - 1)) {
				return false;
			}
		}
		
		return true;
	}
	
	public static Color makeBrighter(Color color) {
			Color c = new Color(color.getRed() + (Constants.COLOR_PERCEPTION_DISTANCE),
					color.getGreen() + (Constants.COLOR_PERCEPTION_DISTANCE),
					color.getBlue() + (Constants.COLOR_PERCEPTION_DISTANCE));
			if (c.equals(Color.white)) {
				//System.out.println("Color is white!");
				c = Color.black;
			}
			//System.out.println("C is now brighter: " + c);
			return c;
		
	}
	
	// TODO idea: implement addColor method which checks to see if a color clashes with 
	// another color already in this color array

}
